<!--
 * @Description  :
 * @Author       : XiaoLei
 * @Date         : 2020-09-02 16:08:15
 * @LastEditTime: 2021-08-03 17:21:56
 * @LastEditors: scg
-->
<template>
  <ASpin :spinning="loading">
    <a-form
      ref="formRef"
      :model="formData"
      :layout="layout"
      :class="{ 'form-inline': layout === 'inline', 'form-flex': itemFlex }"
      v-bind="{ ...defaultCol, ...$attrs }"
      @submit="submit"
    >
      <a-row justify="space-between" :gutter="16" align="end">
        <template v-for="item in formFields">
          <ACol
            v-if="getShow(item)"
            :key="item?.key"
            :span="layout === 'inline' ? 0 : item.span || span"
          >
            <a-form-item
              v-bind="item?.formItemAttrs"
              :label="item?.label || item?.key"
              :field="item?.key"
              :rules="getRules(item)"
            >
              <template v-if="item?.slot">
                <slot
                  :name="item?.slot"
                  :rules="getRules(item)"
                  :attrs="item?.attrs"
                  :tag="item?.tag"
                  :value="formData[item?.key]"
                />
              </template>
              <template v-else-if="item?.tag">
                <component
                  :is="item?.tag"
                  v-model="formData[item?.key]"
                  :size="(item?.attrs && item?.attrs?.size) || size"
                  :placeholder="
                    (item?.attrs && item?.attrs?.placeholder) ||
                    item?.label ||
                    item?.key
                  "
                  v-bind="item?.attrs"
                  v-on="item?.on || {}"
                >
                  <!-- <template
                  v-for="(render, key) of item?.children"
                  #[key]="...rest"
                  :key="key"
                >
                  <Render :key="key" :render="render" :slot-data="rest" />
                </template> -->
                </component>
              </template>
              <template v-else>
                {{ value[item?.key] }}
              </template>
            </a-form-item>
          </ACol>
        </template>
        <ACol v-if="showBtn" :span="layout === 'inline' ? 0 : 24">
          <a-form-item
            v-if="fields.length"
            style="margin: 0"
            :label-col-props="{ span: 0 }"
            :wrapper-col-props="{ span: 24 }"
          >
            <slot name="submit">
              <AButtonGroup v-if="layout === 'inline'" :size="btnSize">
                <AButton
                  v-if="$attrs.submit"
                  type="primary"
                  icon="search"
                  :loading="loading"
                  html-type="submit"
                >
                  {{ okText || '搜索' }}
                </AButton>
                <AButton v-if="$attrs.cancel" icon="reload" @click="cancel">
                  {{ cancelText || '重置' }}
                </AButton>
              </AButtonGroup>
              <div v-else style="text-align: center; width: 100%">
                <AButton
                  :size="btnSize"
                  style="margin-right: 10px"
                  @click="cancel"
                >
                  {{ cancelText || '取消' }}
                </AButton>
                <AButton
                  :size="btnSize"
                  type="primary"
                  html-type="submit"
                  :loading="loading"
                >
                  {{ okText || '提交' }}
                </AButton>
              </div>
            </slot>
          </a-form-item>
        </ACol>
      </a-row>
      <a-form-item v-if="layout === 'inline'" class="form-extra">
        <ASpace>
          <slot name="extra" />
        </ASpace>
      </a-form-item>
    </a-form>
  </ASpin>
</template>

<script lang="ts" setup>
  import {
    ref,
    toRaw,
    reactive,
    computed,
    watch,
    useSlots,
    defineExpose,
  } from 'vue';

  // import Render from './Render';

  interface Filed {
    key: string;
    label: string;
    tag: string;
    decorator?: any;
    attrs?: any;
    formItemAttrs?: any;
    slot?: any;
    on?: any;
    span?: any;
  }

  const props = defineProps({
    value: {
      type: Object,
      default: () => ({}),
    },
    layout: {
      type: String,
      validator: (value: string) =>
        ['horizontal', 'vertical', 'inline'].includes(value),
      default: 'horizontal',
    },
    fields: {
      type: Array,
      default: () => [],
    },
    size: {
      type: String,
      validator: (value: string) =>
        ['mini', 'small', 'medium', 'large'].includes(value),
      default: 'medium' as any,
    },
    cancelText: String,
    okText: String,
    loading: Boolean,
    itemFlex: {
      type: Boolean,
      default: true,
    },
    span: {
      type: Number,
      default: 24,
    },
    showBtn: {
      type: Boolean,
      default: true,
    },
  });
  const emit = defineEmits(['error', 'update:loading', 'submit', 'cancel']);
  const slots: { [x: string]: any } = toRaw(useSlots());
  const formData: { [x: string]: any } = reactive({});
  const formRef = ref();
  const formFields: Array<any> = ref(
    props.fields.map((item) => {
      const temp: any = toRaw(item);
      formData[temp.key] = props.value[temp.key];
      return temp;
    })
  ) as any;
  const getShow = computed(() => {
    // eslint-disable-next-line func-names
    return function (item: any) {
      let show = true;
      if (typeof item.show === 'function') {
        show = item.show({ ...props.value, ...formData }, item, props.fields);
      }
      return show;
    };
  });
  const btnSize = computed(() => {
    const { size } = props;
    return size as any;
  });
  const defaultCol = computed(() => {
    const config: { [x: string]: any } = {
      horizontal: {
        'label-col-props': { sm: 5, lg: 4 },
        'wrapper-col-props': { sm: 19, lg: 20 },
      },
      inline: {
        'label-col-props': {},
        'wrapper-col-props': {},
      },
      vertical: {
        'label-col-props': { lg: 7 },
        'wrapper-col-props': { lg: 17 },
      },
    };
    return config[props.layout];
  });

  watch(
    () => props.value,
    (newVal) => {
      props.fields.forEach((item: any) => {
        // 没有slot且没有tag，则直接显示value[key],不能setvalue,防止aform组件报错
        if (!item.slot && !item.tag) return;
        // slot没有进行v-decorator注册则不能setvalue,防止aform组件报错
        if (
          item.slot &&
          !slots[item.slot]()?.some(
            (i: { data: { directives: any } }) => !!i.data?.directives
          )
        )
          return;
        const [skey] = item.key.split('.');
        formData[skey] = newVal?.[skey];
      });
    },
    {
      deep: true,
    }
  );

  const getRules = ({ decorator = {} }: Filed) => {
    return decorator?.rules || [];
  };
  const getData = (data = formData) => {
    const dataKeys = Object.keys(data);
    const temp: { [x: string]: any } = {};
    for (let i = 0; i < dataKeys.length; i += 1) {
      const dataKey = dataKeys[i];
      temp[dataKey] = data[dataKey];
    }
    return temp;
  };
  const validate = () => {
    formRef.value.validate((errors: any) => {
      if (errors) {
        emit('error', getData(errors), getData());
        emit('update:loading', false);
      } else {
        emit('submit', getData());
      }
    });
  };

  const submit = ({ errors, values }: { errors: any; values: any }) => {
    if (errors) {
      emit('error', getData(errors), getData(values));
      emit('update:loading', false);
    } else {
      // 将空字符串的值重置为 undefined
      Object.keys(values).forEach((key) => {
        if (values[key] === '') values[key] = undefined;
      });
      // eslint-disable-next-line no-unreachable
      emit('submit', getData(values));
    }
  };
  const cancel = () => {
    formRef.value.resetFields();
    emit('cancel', getData(formData));
  };
  defineExpose({
    getData,
    submit,
    validate,
    cancel,
  });
</script>

<style lang="less" scoped>
  .arco-spin {
    display: block !important;
  }
  .form-inline {
    padding: 10px 0 10px 16px;
    border-radius: 4px;
    overflow: hidden;
  }
  :deep(.form-extra) {
    float: right;
  }
  .form-flex .ant-row.ant-form-item {
    display: flex;
    flex-flow: row nowrap;
    justify-content: space-between;
  }
</style>
